package chapter7;

import java.util.*;
import java.io.*;
import java.nio.file.*;
import java.util.logging.*;

public class Main {
    static public void main(String[] args) throws IOException {
        B b = new B();
        b.f(1);

        Logger.getGlobal().info("global"); // 使用一个已有的全局日志记录器对象
        // 通常有7个日志级别：SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST。默认情况下，只记录前三个级别
        Logger.getLogger("").setLevel(Level.OFF); // 关闭所有级别。使用Level.ALL可开启
        // getGlobal()即getLogger("")，祖先日志记录器，名为""
        Logger.getGlobal().info("global(closed)"); // 这一条不会显示(前一条info则会输出)
        
        Logger.getLogger("MyLogger").fine("mine"); // 根据名称获取日志记录器，若不存在，则创建
        // 我们的日志记录器是祖先日志记录器的子类。
        // 日志记录器->父处理器->祖先处理器(名为"")->ConsoleHandler(行为是打印到控制台)
        // logging.下有三种处理器：ConsoleHandler, FileHandler, SocketHandler
        
        var logger = Logger.getLogger("MyLogger");
        logger.setLevel(Level.FINE); // 设置级别为FINE，但只是记录。处理器默认INFO，也需要设置
        logger.setUseParentHandlers(false); // 不使用父处理器
        var handler = new FileHandler(); // pattern, limit, count, append
        handler.setLevel(Level.FINE); // 设置级别为FINE
        logger.addHandler(handler);

        logger.fine("my handler"); // 默认位置是用户主目录(~/java0.log)，默认格式是XML
        // 更多配置，详见原文
    }
}

class A {
    void f() throws EOFException {
        // 可以只声明异常，不论有没有可能抛出
    }
}

class B extends A {

    //void f() throws FileNotFoundException {} // 不能在子类声明超类方法没有声明的异常

    void copyData() throws IOException, MyExpection {
        // ERROR类和RuntimeException类及其子类是非检查性异常。对于后者，一般是我们自己的问题
        int expectlen = 10;
        var in = new Scanner(Path.of("in.txt")); // 此方法抛出检查型异常IOException，要么继续抛给上一个方法，要么捕获
        try(
            // 小括号中用分号分隔的每个变量，会在try退出时，自动调用xxx.close()
            in; // 可以提供事实最终变量
            var out = new PrintWriter("out.txt"); // 可能抛出FileNotFoundException和SecurityException
            // 前者是IOException的子类；后者是RuntimeException的子类，是非检查型异常
        )
        {                                 
            while(in.hasNextLine()){
                String s = in.nextLine();
                out.println(s);
                expectlen -= s.length();
            }
            // 如果上面代码出现异常时，又逢close()执行失败，close抛出的异常会当前异常抑制
        }
        catch (FileNotFoundException | IllegalStateException e) { // 一个catch子句可以捕获多个异常类型
            e.printStackTrace(); // 向System.err打印堆栈轨迹
            e.addSuppressed(new MyExpection("作示例用")); // 添加一个被抑制的异常
            Logger.getLogger("com.blover.chapter7").log(Level.SEVERE, "example", e); // 可以把日志记录器命名为与主应用包一致
            // SERVER级别用来指示严重错误
            // 我们定义的记录器是祖先日志记录器的子类，用的还是祖先记录器的处理器。
        }
        catch(@SuppressWarnings("hiding") IOException e){
            e.printStackTrace();
            var re = new RuntimeException("其他IO异常");
            re.initCause(e); // 设置为新异常的“原因”
            throw re; // 重新抛出
            // 如果包装成运行时异常（非检查型异常），就可以绕开超类的异常声明限制
        }
        finally{
            // 无论try语句块中是否有异常，finally子句一定会执行。如果finally子句有return，会覆盖try中的return
            // 所以不应该把会改变程序流程的代码写在这
            System.out.println("finally");
        }
        // 关于栈帧分析，更多见原文

        if(expectlen > 0){
            throw new EOFException(); // 读到的字符数不符合最小期望，我们抛出这个异常，表示“输入过程中意外遇到EOF”
        }
        if(expectlen < 0){
            throw new MyExpection();
        }
    }

    void f(int n) {
        // 默认情况下禁用断言（类加载器会移除相关代码）。为java命令添加 -ea(或-enableassertions) 以启用（也可以为某个类启用或禁用断言，详见原文）
        assert n > 0; // 当表达式值为false时，抛出AssertError异常
        assert n > 1 : n; // 对于冒号后的表达式，会结果将作为字符串，打印在输出的消息中
        // 断言检查应该只在开发和测试阶段打开
    }
}

// 定义自己的异常类
class MyExpection extends IOException {
    public MyExpection() {}
    public MyExpection(String s) {
        super(s);
    }
}

// 非“运行时异常”不是真的不发生在运行时。RuntimeException有点误导